import { IProp } from '../../types/vue-sfc';
import { ASTElement, ASTText } from 'vue-template-compiler';

import {
    isObjectProperty,
    isIdentifier,
    ObjectExpression,
    ObjectProperty,
    isObjectExpression,
    isStringLiteral,
    isBooleanLiteral,
    isBinaryExpression,
    ObjectMethod,
    isObjectMethod,
    ExpressionStatement,
    isCallExpression,
    isMemberExpression,
    isNumericLiteral,
    isNullLiteral
} from '@babel/types';

import {
    getStartSpaceLen,
    getEndSpaceLen,
    randomCode,
    getSpaceChars,
    isNotEmptyObject,
    isFunctionType,
    isObjectType,
    formatCodeForScript,
    fixEndSemicolon,
    formatCodeForArray,
    formatCodeForObject,
    formatCodeForAnonymousFunction
} from '../../utils';

/**
 * 递归搜寻 Slot 节点
 */
export function findSlotNodes(node: ASTElement, code: string) {
    // ...
    let index = 0;
    const sourceCode = code;
    const sourceCodeLength = sourceCode.length;

    const result: Array<{
        name: string;
        node: ASTElement;
        start: number;
        end: number;
        sourceText: string;
        comment:
            | undefined
            | {
                  text: string;
                  fullText: string;
                  start: number;
                  end: number;
              };
    }> = [];

    function handle(node: ASTElement) {
        if (node.tag === 'slot') {
            const startTag = '<slot';
            const startTagFlag = `<${index}_slot`;
            const endTag = '</slot>';
            const endTagFlag = `</slot_${index}>`;
            code = code.replace(startTag, startTagFlag);
            code = code.replace(endTag, endTagFlag);

            // 构造模式匹配
            let slotRegTxt = startTagFlag;
            if (node.slotName) {
                slotRegTxt += `.*name=.${node.slotName.replace(/"/g, '')}.*`;
            } else if (node.attrsList.length) {
                for (const attr of node.attrsList) {
                    slotRegTxt += `.*${attr.name}=.${attr.value}.`;
                }
            }
            slotRegTxt += `>.*${endTagFlag}`;
            index++;

            const splitResult = code.split(new RegExp(slotRegTxt, 'gmsy'));
            if (splitResult.length > 1) {
                const [startText, endText] = splitResult;
                const start = startText.length;
                const end = sourceCodeLength - endText.length;
                const sourceText = sourceCode.substring(start, end);
                result.push({
                    name: getSlotName(node),
                    node,
                    start,
                    end: end - (index < 10 ? index * 4 : index * 6),
                    sourceText,
                    comment: getSlotCommentByNode(node, sourceCode.split(sourceText)[0])
                });
            }

            // ...
        } else if (node.children) {
            for (const child of node.children) {
                handle(<ASTElement>child);
            }
        }
    }

    handle(node);
    return result;
}

/**
 * 获取 Slot 注释信息
 */
export function getSlotCommentByNode(slotNode: ASTElement, code: string) {
    let info:
        | undefined
        | {
              text: string;
              fullText: string;
              start: number;
              end: number;
          };

    const parentNode = slotNode.parent;

    if (parentNode?.children) {
        const childrens = parentNode.children;
        childrens.forEach((child, index) => {
            // @ts-ignore
            if (child?.tag === 'slot' && slotNode.slotName === child.slotName) {
                const commentNode = childrens[index - 1] as ASTText;
                if (commentNode && commentNode.isComment) {
                    const text = (commentNode.text || '').trim();
                    const fullText = `<!--${commentNode.text}-->`;
                    const start = code.lastIndexOf(fullText);
                    const end = code.length;
                    info = { text, fullText, start, end };
                }
            }
        });
    }

    return info;
}

/**
 * 获取 Emit 事件信息
 */
export function getEmitEventInfo(statement: ExpressionStatement) {
    const info: { event?: string } = {};
    if (isCallExpression(statement.expression)) {
        statement.expression.arguments.forEach((arg, index) => {
            if (index === 0 && isStringLiteral(arg) && arg.value) {
                const name = arg.value;
                if (name) info.event = name;
            }
        });
    }
    return isNotEmptyObject(info) ? info : undefined;
}

/**
 * 获取 Slot 名称
 */
export function getSlotName(slotNode: ASTElement) {
    return (slotNode.slotName || 'default').replace(/"/g, '');
}

/**
 * 获取 Prop Name
 */
export function getObjectPropName(prop: ObjectExpression['properties'][0]) {
    let name = null;
    if (isObjectProperty(prop)) {
        if (isIdentifier(prop.key)) {
            name = prop.key.name;
        }
    }
    return name;
}

/**
 * 获取函数名
 */
export function getMethodName(method: ObjectMethod) {
    let name = null;
    if (isObjectMethod(method)) {
        if (isIdentifier(method.key)) {
            name = method.key.name;
        }
    }
    return name;
}

/**
 * 是否 Event
 */
export function isEmitEvent(statement: ExpressionStatement) {
    const callName = getMethodCallName(statement);
    return callName === '$emit';
}

/**
 * 获取函数调用名
 */
export function getMethodCallName(statement: ExpressionStatement) {
    let name = '';
    if (isCallExpression(statement.expression)) {
        if (isMemberExpression(statement.expression.callee)) {
            if (isIdentifier(statement.expression.callee.property)) {
                name = statement.expression.callee.property.name;
            }
        }
    }
    return name;
}

/**
 * 获取 Options 选项的 Tab Size
 */
export function getOptionTabSize(code: string, options: any) {
    let optionTabSize = 0;
    const firstOption = options[0];
    if (firstOption) {
        const t = code.substring(0, firstOption.start);
        optionTabSize = getEndSpaceLen(t);
    }
    return optionTabSize;
}

/**
 * 格式化模板内容
 */
export function formatTemplateContent(templateContent: string) {
    templateContent = templateContent.replace(/^\r?\n*/, '');
    templateContent = templateContent.replace(/\r?\n*$/, '');
    const lines = templateContent.split('\n');

    const spaceLen = getStartSpaceLen(templateContent, 2);
    const spaceChars = Array(spaceLen).fill(' ').join('');

    const newTemplateContent = lines
        .map(line => {
            return `${line.trim() && spaceChars}${line}`;
        })
        .join('\n');

    return `\n${newTemplateContent}\n`;
}

/**
 * 格式化 Prop 对象中字段值的格式
 */
export function formatPropFieldValue(valueContent: string, spaceLen: number) {
    const lines = valueContent.split('\n');
    const spaceChars = Array(spaceLen).fill(' ').join('');

    let newValueContent = lines
        .map((line, idx) => {
            if (idx === 0) {
                return line;
            } else {
                return `${line.trim() && spaceChars}${line}`;
            }
        })
        .join('\n');

    newValueContent = newValueContent.replace(/\r?\n+$/, '');

    return newValueContent;
}

/**
 * 替换注释为标识位
 */
export function replaceCommentToFlag(code: string, nodePath: ObjectProperty | ObjectMethod | ExpressionStatement) {
    if (nodePath.leadingComments) {
        const firstComment = nodePath.leadingComments[0];
        const lastComment = nodePath.leadingComments[nodePath.leadingComments.length - 1];

        const oldCommentStart = firstComment.start;
        const oldCommentEnd = lastComment.end;
        const oldCommentText = code.substring(oldCommentStart, oldCommentEnd);

        const key = randomCode(oldCommentText.length);
        const startText = code.substring(0, oldCommentStart);
        const endText = code.substring(oldCommentStart).replace(oldCommentText, key);
        code = startText + endText;

        const spaceLen = getEndSpaceLen(startText);

        return { code, key, startText, endText, spaceLen, start: firstComment.start };
    }
}

/**
 * 添加注释的处理操作
 */
export function addCommentHandle(code: string, nodePath: ObjectProperty | ObjectMethod | ExpressionStatement) {
    const key = randomCode(10);
    const insetOffset = nodePath.start || 0;
    const startText = code.substring(0, insetOffset);
    const spaceLen = getEndSpaceLen(startText);
    return { key, insetOffset, spaceLen };
}

/**
 * 更新或新增 Prop 对象的字段值
 */
export function upsetPropFieldValue(params: { code: string; prop: IProp; nodePath: ObjectProperty; keyName: string; replaceStrMap: any; optionTabSize: number }) {
    try {
        if (isObjectExpression(params.nodePath.value)) {
            const properties = params.nodePath.value.properties;

            // @ts-ignore
            const fieldNode = properties.find(f => f.key.name === params.keyName);

            // @ts-ignore
            let replaceValue = params.prop[params.keyName];

            // 调整 replaceValue 的值
            if (params.keyName === 'type') {
                replaceValue = replaceValue.join(' | ').trim();
                if (!replaceValue) {
                    throw new Error('prop.type 值为空');
                }

                // ...
            } else if (typeof replaceValue === 'string') {
                // ...
                // 处理 default 字段值，格式化
                if (params.keyName === 'default' && params.prop.defaultValueType && ['function', 'object', 'array'].includes(params.prop.defaultValueType)) {
                    const nodeStartText = params.code.substring(0, params.nodePath.start || 0);
                    const nodeSpaceLen = getEndSpaceLen(nodeStartText);
                    const tabWidth = nodeSpaceLen - params.optionTabSize;
                    switch (params.prop.defaultValueType) {
                        case 'function': {
                            const isAnonymousFunction = replaceValue.trim().match(/^function\s*\(\)/);
                            if (isAnonymousFunction) {
                                replaceValue = formatCodeForAnonymousFunction(replaceValue, { tabWidth });
                            } else {
                                replaceValue = formatCodeForScript(replaceValue, { tabWidth });
                            }
                            replaceValue = fixEndSemicolon(replaceValue);
                            break;
                        }
                        case 'object': {
                            replaceValue = formatCodeForObject(replaceValue, { tabWidth });
                            replaceValue = fixEndSemicolon(replaceValue);
                            break;
                        }
                        case 'array': {
                            replaceValue = formatCodeForArray(replaceValue, { tabWidth });
                            replaceValue = fixEndSemicolon(replaceValue, { isArray: true });

                            break;
                        }
                    }
                    replaceValue = formatPropFieldValue(replaceValue, tabWidth * 2 + params.optionTabSize);

                    // ...
                } else {
                    const isSingleQuote = replaceValue.match(/^'/);
                    const isMultiLine = replaceValue.split('\n').length > 1;
                    if (isMultiLine) {
                        replaceValue = `\`${replaceValue}\``;
                    } else if (isSingleQuote) {
                        replaceValue = `"${replaceValue}"`;
                    } else {
                        replaceValue = `'${replaceValue}'`;
                    }
                }
            }

            if (fieldNode) {
                if (isObjectProperty(fieldNode)) {
                    // ...
                    let key = '';
                    if (isIdentifier(fieldNode.value)) {
                        key = randomCode(fieldNode.value.name.length);
                    } else if (isStringLiteral(fieldNode.value)) {
                        // @ts-ignore
                        key = randomCode(fieldNode.value.extra.raw.length);
                    } else if (isNumericLiteral(fieldNode.value)) {
                        key = randomCode(('' + fieldNode.value.value).length);
                    } else if (isBooleanLiteral(fieldNode.value)) {
                        key = randomCode(('' + fieldNode.value.value).length);
                    } else if (isNullLiteral(fieldNode.value)) {
                        key = randomCode(4);
                    } else if (isBinaryExpression(fieldNode.value)) {
                        const t = params.code.substring(fieldNode.value.start || 0, fieldNode.value.end || 0);
                        key = randomCode(t.length);
                    } else {
                        const { start, end } = fieldNode.value;
                        if (start !== null && end !== null) {
                            const content = params.code.substring(start, end);
                            key = randomCode(content.length);
                        }
                    }

                    if (key) {
                        const start = fieldNode.value.start || 0;
                        const end = fieldNode.value.end || 0;
                        const startText = params.code.substring(0, start);
                        const endText = params.code.substring(end);
                        params.code = startText + key + endText;
                        params.replaceStrMap[key] = { text: replaceValue, offset: start, operType: 'replace' };
                    }
                }

                // ...
            } else {
                const key = randomCode(10);
                const startText = params.code.substring(0, params.nodePath.start || 0);
                const spaceLen = getEndSpaceLen(startText);

                let addOffset = 1;
                const objText = params.code.substring(params.nodePath.value.start || 0, params.nodePath.value.end || 0);
                if (objText.split('\n').length > 1) addOffset = 2;
                if (objText.split('\r\n').length > 1) addOffset = 3;

                const replaceContent = `${getSpaceChars(spaceLen + params.optionTabSize)}${params.keyName}: ${replaceValue},\n`;
                const insetOffset = (params.nodePath.value.start || 0) + addOffset;
                params.replaceStrMap[key] = { text: replaceContent, offset: insetOffset, operType: 'inset' };
            }
        }

        // ...
    } catch (error) {
        // ...
    }

    return { code: params.code };
}

/**
 * 设置默认值的原始类型
 */
export function setDefaultValueType(prop: IProp) {
    if (isObjectType(prop.default)) {
        throw new Error('default 属性值如果传入对象类型，请字符串化后传入');
    }
    if (Array.isArray(prop.default)) {
        throw new Error('default 属性值如果传入数组类型，请字符串化后传入');
    }
    if (isFunctionType(prop.default)) {
        throw new Error('default 属性值如果传入函数类型，请字符串化后传入');
    }

    const isArray = () => {
        const isMatch = (prop.default as string).trim().match(/^\[/);
        return !!isMatch;
    };

    const isObject = () => {
        const isMatch = (prop.default as string).trim().match(/^\{/);
        return !!isMatch;
    };

    const isFunction = () => {
        const reg1 = (prop.default as string).trim().match(/^function/);
        const reg2 = (prop.default as string).trim().match(/^\(\)\s{0,3}\=\>/);
        return !!reg1 || !!reg2;
    };

    if (typeof prop.default === 'string') {
        prop.defaultValueType = 'string';

        if (isArray()) {
            prop.defaultValueType = 'array';
        } else if (isObject()) {
            prop.defaultValueType = 'object';
        } else if (isFunction()) {
            prop.defaultValueType = 'function';
        }

        // ...
    } else if (typeof prop.default === 'boolean') {
        prop.defaultValueType = 'boolean';
    } else if (typeof prop.default === 'number') {
        prop.defaultValueType = 'number';
    } else if (prop.default === null) {
        prop.defaultValueType = 'null';
    } else if (prop.default === undefined) {
        prop.defaultValueType = 'undefined';
    } else {
        prop.defaultValueType = 'unknown';
    }
}
